/*
 * usbStdio.c
 *
 *  Created on: 2013. 7. 24.
 *      Author: Shim
 */

/* Includes ------------------------------------------------------------------*/
#include "stdint.h"
#include "stm32f4_discovery.h"
#include "stm32f4xx_it.h"

#include "usbd_cdc_core.h"
#include "usbd_usr.h"
#include "usbd_desc.h"
#include "usbd_cdc_vcp.h"

#include <stdarg.h>
/* Private typedef -----------------------------------------------------------*/
/* Private define ------------------------------------------------------------*/
/* Private macro -------------------------------------------------------------*/
/* Private variables ---------------------------------------------------------*/
/**
 * @brief 0 ~ 15 사이의 정수값을 ASCII 캐릭터에 매핑시키기 위한 배열
 */
static const char * const hex = "0123456789abcdef";

__ALIGN_BEGIN USB_OTG_CORE_HANDLE  USB_OTG_dev __ALIGN_END;
/* Private function prototypes -----------------------------------------------*/



/* Private functions ---------------------------------------------------------*/

/**
  * @brief  USB IO Initialization
  * @param  NONE
  * @retval NONE
  * USB VCP를 이용한 통신 설정을 초기화한다.
  */
void usbInit(void)
{
	USBD_Init(&USB_OTG_dev,
		#ifdef USE_USB_OTG_HS
			USB_OTG_HS_CORE_ID,
		#else
			USB_OTG_FS_CORE_ID,
		#endif
			&USR_desc,
			&USBD_CDC_cb,
			&USR_cb);

	VCP_Init();
}

/**
  * @brief  put a character to USB VCP
  * @param  ch 전송하고자 하는 문자
  * @retval USBD_OK (0) 캐릭터 전송 성공
  * VCP를 통해 1개의 문자를 전송한다.
  */

int32_t usbPutchar(int8_t ch) {
	return (int32_t)VCP_DataTx(0, ch);
}

/**
  * @brief  put a characters array to USB VCP
  * @param  str 전송하고자 하는 문자열
  * @retval 전송한 문자열의 갯수
  * VCP를 통해 1개의 문자열을 전송한다. 문자열은 '\0' 문자가 나오기 전까지의 길이만큼 전송한다.
  */
int32_t usbPuts(const int8_t * str)
{
	int32_t ret;
	while(*str !='\0') {
		usbPutchar(*str);
		str++;
		ret++;
	}
	return ret;
}

/**
  * @brief  put a characters array to USB VCP
  * @param  buf 전송하고자 하는 문자열
  * @param  len 전송하고자 하는 문자열의 길이
  * @retval 전송한 문자열의 갯수
  * VCP를 통해 길이 len 만큼의 문자열을 전송한다.
  */
int32_t usbWrite(const int8_t *buf, uint32_t len)
{
	uint32_t idx;

	for(idx = 0 ; idx < len ; idx++) {
		if(buf[idx] =='\n') {
			usbPutchar('\r');
		}
		usbPutchar(buf[idx]);
	}

	return idx;
}

/**
  * @brief  printf implementation for  USB VCP
  * @param  str 전송하고자 하는 문자열
  * @param  ... printf문의 선택 인자
  * @retval NONE
  * VCP를 이용한 printf문의 구현
  */
void usbPrintf(const int8_t *str, ...)
{
	uint32_t idx;								/**< 반복문 인덱스 */
	uint32_t value;								/**< 정수형 arg 값을 받아들이기 위한 변수 */
	uint32_t pos;
	uint32_t count;
	uint32_t base;								/**< 진법. 10진수, 16진수 */
	uint32_t neg;								/**< 입력받은 정수가 음수인지 양수인지 표시 */
	int8_t *s;									/**< 문자열 변수 */
	int8_t buf[16];
	int8_t fill;								/**< 출력시 채워넣는 값 */
	va_list arg;								/**< 가변인자 변수 */


	va_start(arg, str);							/**< 가변인자 처리 시작 */

	while(*str != '\0') {
		/**
		 * 첫번째로 '%' 캐릭터나 문자열 종료가 아닌 캐릭터를 찾는다.
		 */
		for(idx = 0 ; (str[idx] != '%') && (str[idx] != '\0') ; idx++) ;

		usbWrite(str, idx);						/**< 시작점부터 그 위치까지 출력한다. */

		str += idx;

		/**
		 * % 문자열 처리를 수행한다.
		 */
		if(*str == '%') {
			str++;

			count = 0;
			fill = ' ';

again:
			switch(*str++) {
			case '0':
			case '1':
			case '2':
			case '3':
			case '4':
			case '5':
			case '6':
			case '7':
			case '8':
			case '9':
				/**
				 * 만일 맨 앞자리 숫자가 0이면 캐릭터 '0'을 채워넣는다.
				 */
				if( (str[-1] =='0') && (count == 0) ) {
					fill = '0';
				}

				/**
				 * 자리수를 갱신한다.
				 */
				count *= 10;
				count += str[-1] - '0';
				goto again;

			case 'c':
				value = va_arg(arg, uint32_t);	/**< arg의 값을 가져온다. */
				/**
				 * 캐릭터를 출력한다.
				 */
				usbPutchar((uint8_t)value);
				break;

			case 'd':
			case 'i':							/**< %d와 %i 처리 - 정수 */
				value = va_arg(arg, uint32_t);	/**< arg의 값을 가져온다. */
				/**
				 * 버퍼의 위치를 리셋한다.
				 */
				pos = 0;
				/**
				 * 만일 입력값이 음수이면 숫자는 양수로 바꾸어주고
				 * '-' 부호를 붙여준다.
				 */
				if(value < 0) {
					value = -value;
					/**
					 * 음수임을 표시한다.
					 * neg == 0 : 양수, neg == 1 음수
					 */
					neg = 1;
				} else {
					neg = 0;
				}
				base = 10; 						/**< 베이스를 10으로 설정한다. */

				goto convert;					/**< ASCII로 변환하는 곳으로 이동한다. */


			case 's':			/**<%s 처리 - 문자열 */
				s = va_arg(arg, int8_t *);	/**< arg의 값을 가져온다. */

				usbPuts(s);

				/**
				 * 요구하는 만큼의 추가 공간을 출력한다.
				 */
				if(count > idx) {
					count -= idx;
					while(count--) {
						usbWrite((int8_t*)" ", 1);
					}
				}
				break;

			case 'u':		/**< %u 처리. 양의 정수 */
				value = va_arg(arg, uint32_t);	/**< arg의 값을 가져온다. */
				pos = 0;						/**< 버퍼의 위치를 리셋한다. */
				base = 10;						/**< 베이스를 10으로 설정한다. */
				neg = 0;						/**< 항상 양수이기 때문에 0으로 설정한다. */
				goto convert;					/**< ASCII로 변환하는 곳으로 이동한다. */

			case 'x':
			case 'X':
			case 'p':		/**< %x 처리. 16진수 출력 */
				value = va_arg(arg, uint32_t);	/**< arg의 값을 가져온다. */
				pos = 0;						/**< 버퍼의 위치를 리셋한다. */
				base = 16;						/**< 베이스를 16으로 설정한다. */
				neg = 0;						/**< 항상 양수이기 때문에 0으로 설정한다. */

convert:
				for(idx = 1 ;
					(((idx * base) <= value) &&
					(((idx * base)/base) == idx));
					idx *= base, count--);

				if(neg) {
					count--;
				}

				if(neg && (fill == '0')) {
					buf[pos++] = '-';
					neg = 0;
				}

				if((count > 1) &&(count < 16)) {
					for(count--; count; count--) {
						buf[pos++] = fill;
					}
				}

				/**
				 * 만일 value가 dmatndlaus '-' 부호를 숫자 앞에 붙인다.
				 */
				if(neg) {
					buf[pos++] = '-';
				}

				/**
				 * value를 문자열로 변환한다.
				 */
				for(;idx ; idx /= base) {
					buf[pos++] = hex[(value / idx) % base];

				}

				usbWrite(buf, pos);			/**< 문자열을 출력한다. */
				break;
			case '%':		/**< %% 처리 */
				usbPutchar('%');
				break;
			default:
				usbPuts((int8_t*)"ERROR");
				break;
			}
		}
	}

	va_end(arg);
}

/**
  * @brief  get a character from USB VCP
  * @param  NONE
  * @retval VCP로부터 획득한 문자
  * VCP를 통해 길이 1개의 문자를 수신한다.
  */
int8_t usbGetchar(void)
{
	int8_t ret = 0;
	while(bufferRead(&vcp_buf, &ret) != 0);
	return ret;

}

/**
  * @brief  get a character string from USB VCP
  * @param  buf VCP를 통해 수신한 문자를 저장할 배열의 포인터
  * @param  len VCP로부터 수신하고자 하는 최대 문자의 갯수
  * @retval 실제로 수신한 문자열의 갯수
  * VCP를 통해 문자열을 수신한다.
  * '\r' 또는 '\n'이 수신될 때 까지 데이터를 수신한다.
  * 백스페이스 문자는 자신과 이전 문자를 삭제하는 기능을 수행한다.
  */
int32_t usbGets(int8_t *buf, int32_t len)
{
	uint32_t count = 0;
	int8_t ch;
	static int8_t lastWasCR = 0;

	len--;				/**< NULL 종료문자를 제외하기 위해 len을 1만큼 줄인다. */

	/**
	 * CR 또는 LF가 입력될 때까지 진행한다.
	 */
	while(1) {
		ch = usbGetchar();	// 한개의 문자를 입력받는다.

		if(ch == '\b')
		{
			if(count) {
				usbWrite((int8_t*)"\b \b", 3);
				count--;
			}
			// 다음 문자열을 읽는 것은 생략한다.
			continue;
		}

		/**
		 * 만약 문자가 LF이고 이전 문자가 CR이면
		 */
		if((ch == '\n') && lastWasCR) {
			lastWasCR = 0;
			continue;
		}

		if((ch == '\r') || (ch == '\n') || (ch == 0x1b)) {
			if(ch == '\r') {
				lastWasCR = 1;
			}
			/**
			 * 입력 처리를 멈춘다.
			 */
			break;
		}

		if(count < len) {
			buf[count] = ch;
			count++;
			usbPutchar(ch);		/**< echo character */
		}
	}

	buf[count] = 0;				/**< 문자열 종료 표시 */
	usbPuts((int8_t*)"\r\n");

	return count;
}
